home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Experimental BBS Explossion 3
/
Experimental BBS Explossion III.iso
/
c
/
bc_pas_1.zip
/
PCMIOC.C
< prev
next >
Wrap
Text File
|
1993-01-06
|
50KB
|
1,812 lines
/*$Author: DCODY $*/
/*$Date: 06 Jan 1993 15:26:34 $*/
/*$Header: X:/sccs/pcm/pcmioc.c_v 1.11 06 Jan 1993 15:26:34 DCODY $*/
/*$Log: X:/sccs/pcm/pcmioc.c_v $
*
* Rev 1.11 06 Jan 1993 15:26:34 DCODY
* corrected two bugs: ContinueThisBlockOutput had a bug in an IF statement
* where it evaluated the two expressions incorrectly. The second bug was
* in QueryPCMStream, which returned an incorrect value.
* Also, some debugging info was added, but commented out.
*
* Rev 1.10 08 Dec 1992 17:13:54 DCODY
* added a new routine: QueryPCMStream to return the number of blocks
* buffered.
* Also added, but commented out some debugging code.
*
* Rev 1.9 20 Oct 1992 10:07:38 DCODY
* lots of cosmetic changes. all variables are now initialized so they
* have memory allocated at compile time.
*
* Rev 1.8 06 Oct 1992 15:59:50 DCODY
* major changes so code is free (freer) from the C libraries. Replaced
* malloc and fread/fwrite.
*
* Rev 1.7 01 Oct 1992 12:05:02 DCODY
* next stage of completion for PlayThisBlock, RecordThisBlock, etc.
*
* Rev 1.6 23 Sep 1992 10:56:34 DCODY
* more work on playthisblock, continuethisblock...
*
* Rev 1.5 26 Aug 1992 10:57:30 DCODY
* Added Playthisblock and RecordThisBlock
*
* Rev 1.4 12 Aug 1992 17:10:30 DCODY
* major change to eliminate the foreground buffers.
*
* Rev 1.3 24 Jul 1992 15:36:14 DCODY
* changed _fmemcpy to _rfmemcpy
*
* Rev 1.2 17 Jul 1992 14:22:50 DCODY
* InitMVSound() now performed within OpenPCMBuffering().
*
* Rev 1.1 23 Jun 1992 17:11:42 DCODY
* PAS2 update
*
* Rev 1.0 15 Jun 1992 09:44:38 BCRANE
* Initial revision.
*/
/*$Logfile: X:/sccs/pcm/pcmioc.c_v $*/
/*$Modtimes$*/
/*$Revision: 1.11 $*/
/*$Workfile: pcmioc.c $*/
; /*\
;---|*|----====< PCMIOC.C >====----
;---|*|
;---|*| These routines maintain DMA controlled I/O of the Audio Spectrum
;---|*|
;---|*| Copyright (c) 1991, Media Vision, Inc. All rights reserved.
;---|*|
; \*/
#include <stdio.h>
#include <stdlib.h>
#include "pcmio.h"
#include "common.h"
#include "mvsound.h"
; /*\
;---|*|-----------====< T H E O R Y O F O P E R A T I O N >====------------
;---|*|
;---|*| The best DMA controlled PCM output requires a continuous stream of data
;---|*| to be available in a real-time environment.
;---|*|
;---|*| DMA controlled PCM input, with the same real-time requirements, needs
;---|*| to be able to keep storing data into memory without pausing.
;---|*|
;---|*| The following approach is designed to allow the DMA to be setup in
;---|*| "auto-initialize" mode, thereby guarenteeing continuous play/record.
;---|*|
;---|*| To keep the DMA running, multiple divisions of the DMA buffer are
;---|*| used to keep the data moving. Due to the fact that DOS is neither
;---|*| a real-time, or re-entrant operating system, this code divides up
;---|*| the buffer management tasks into a "foreground" and "background" task.
;---|*|
;---|*| A sample buffer count timer on the Audio Spectrum is used to interrupt
;---|*| the CPU when a DMA buffer division has filled or emptied. For our
;---|*| purposes here, this amount may be 1/2, 1/4, 1/8th or some smaller
;---|*| division of the whole DMA buffer. Note: judgement must be used here
;---|*| in selecting the DMA buffer size, and the integral division. Too small
;---|*| of an integral may result in broken DMA I/O. A buffer too large never
;---|*| hurts anything. (it just reduces the amount of available memory).
;---|*|
;---|*| ----====< PCM OUTPUT >====----
;---|*|
;---|*| To perform PCM output ("play"), A linked list of buffer pointers is
;---|*| used to fill the DMA buffer by the foregound task. As the DMA runs,
;---|*| it will send the buffer contents to the audio card. Here is a visual:
;---|*|
;---|*|
;---|*| Foreground Loads
;---|*| the Top Level
;---|*| Buffers
;---|*|
;---|*| ┌─┬─┬─┬─┬─┐
;---|*| DMA Level Buffers │ │ │ │ │ │
;---|*| └─┴─┴─┴─┴─┘
;---|*|
;---|*| ┌─────────┐
;---|*| │hardware │
;---|*| └╥╥╥──────┘
;---|*|
;---|*| To actually start the output, the foreground task loads it's
;---|*| buffers, then starts the DMA to play the buffer. The background
;---|*| task only indicates when each block is played. It will shut down
;---|*| the DMA if no more data is present in the buffers.
;---|*|
;---|*| If the foreground task can keep the linked list of buffers full,
;---|*| there should be non-stop PCM output (Good!). If the foreground task
;---|*| does not keep up, the background task will be forced to stop the
;---|*| the DMA, thereby causing a break in the output (Bad!). Once the DMA
;---|*| has stopped, the foreground task will have to restart the DMA a
;---|*| second time to continue the flow of data.
;---|*|
;---|*| ----====< PCM INPUT >====----
;---|*|
;---|*| To perform PCM input ("record"), the same linked list of buffers
;---|*| are also used. This buffer is filled with sampled data from the
;---|*| hardware. The background process will increment a global variable for
;---|*| each buffer filled. The foreground routine must extract each buffer
;---|*| and process it (copy to memory, or write it to disk). Here is a visual:
;---|*|
;---|*|
;---|*| Foreground unloads
;---|*| the Top Level
;---|*| Buffers
;---|*|
;---|*| ┌─┬─┬─┬─┬─┐
;---|*| DMA Level Buffers │ │ │ │ │ │
;---|*| └─┴─┴─┴─┴─┘
;---|*|
;---|*| ┌─────────┐
;---|*| │hardware │
;---|*| └╥╥╥──────┘
;---|*|
;---|*| To actually start the input, the foreground starts the DMA running to
;---|*| begin the transfer. The background task increments the global variable
;---|*| as each interrupt occurs. If all the buffers are full, the DMA transfer
;---|*| is terminated. The foreground routine must poll this variable to keep
;---|*| the data moving out of the DMA buffer.
;---|*|
;---|*| If the foreground task can keep the linked list of buffers empty,
;---|*| there should be non-stop PCM input (Good!). If the foreground task
;---|*| does not keep up, the background task will be forced to stop the
;---|*| the DMA, thereby causing a break in the input (Bad!). Once the DMA
;---|*| has stopped, the foreground task will have to restart the DMA tranfer
;---|*| a second time to restart the DMA.
;---|*|
;---|*| ----====< DATA VARIABLES >====----
;---|*|
;---|*| The following is a description of the variables shared between the
;---|*| foreground and background tasks. There are three global variables,
;---|*| and a linked list of buffers shared between the two tasks.
;---|*|
;---|*| The linked list of buffers uses a "header" to each buffer. This
;---|*| header holds the information for linking to the next buffer, whether
;---|*| the buffer is full or empty, and the count of bytes in the buffer.
;---|*|
;---|*| typedef struct _buffptr {
;---|*| int status; /* 0=empty, 1=full * /
;---|*| int count; /* # of bytes in the buffer * /
;---|*| int size; /* total size of read data * /
;---|*| char huge *buffer; /* pointer to buffer data * /
;---|*| struct _buffptr, *nextptr; /* pointer to next buffer * /
;---|*|
;---|*| } BuffData,*BuffPtr;
;---|*|
;---|*| BuffPtr HeadOfBuffers; /* global variable head pointer * /
;---|*| int BufferDataCount; /* # of full DMA buffers parts * /
;---|*| int DMARunning; /* DMA status (0=off,1=running) * /
;---|*| char far *StartOfDMABuffer; /* start of actual DMA buffer * /
;---|*| int ProcessedBlockCount; /* # of blocks DMA handled * /
;---|*|
;---|*| "HeadOfBuffers" points to the first buffer in the linked list.
;---|*|
;---|*| This linked list is made up of structures containing the buffer
;---|*| data and other information. The last entry in the list points
;---|*| back to the first entry, thereby creating a circular linked
;---|*| list.
;---|*| Each buffer has a status word: 0=empty,1=full.
;---|*| The count indicates the # of bytes in the buffer. This count
;---|*| is used to communication between the foreground and background
;---|*| process that data is available. For output, the count tells the
;---|*| background that data is available to be loaded in the DMA buffer.
;---|*| For input, the count tells the foreground process that there is
;---|*| data to be written to disk.
;---|*|
;---|*| "BufferDataCount" is the key handshaking variable between the
;---|*| foreground and background processes. It indicates how many DMA
;---|*| buffers divisions contain data.
;---|*|
;---|*| For output, it holds a count of DMA divisions hold data. This
;---|*| global variable is incremented each time a buffer is loaded by
;---|*| the foreground task, and decremented when a buffer is emptied
;---|*| by the background task.
;---|*|
;---|*| For input, it holds the number of buffers with data in the DMA
;---|*| buffer. It is incremented by the background process and
;---|*| decremented by the foreground process.
;---|*|
;---|*| "DMARunning" is set to true or false depending upon the state
;---|*| of the DMA channel. It is set TRUE when the DMA is running (either
;---|*| playing or recording), and FALSE when the DMA is turned off.
;---|*|
;---|*| "ProcessedBlockCount" is the running total of blocks the DMA has
;---|*| processed from the last Start I/O call.
;---|*|
;---|*| For input, this is the total number of dma divisions filled
;---|*| by the DMA.
;---|*|
;---|*| For output, this is the total number of blocks loaded into
;---|*| the DMA buffer.
;---|*|
;---|*| "StartOfDMABuffer" points to the first byte of the DMA circular buffer.
;---|*|
;---|*| The following routines provide a high level interface to DMA driven
;---|*| PCM output:
;---|*|
;---|*| int OpenPCMBuffering ( int, int, int, int )
;---|*|
;---|*| This routine is the first routine to be called. It sets
;---|*| up the DMA channel, IRQ, and allocates memory for the buffers.
;---|*|
;---|*| int PCMState ( int, int, int, int )
;---|*|
;---|*| This routine passes in the sample rate, stereo/mono flag,
;---|*| the compression type (0 for 8 bit, 1 for for 4 bit),
;---|*| and the PCM data sample size (8 or 16).
;---|*|
;---|*| int StartFileInput ( FILE *f )
;---|*|
;---|*| This routine begins recording the PCM data to the disk file.
;---|*| The routine returns immediately. The routine,
;---|*| "ContinueFileInput" must be called to continue moving data
;---|*| from the DMA buffer to to the disk.
;---|*|
;---|*| int StartBlockInput ( )
;---|*|
;---|*| This routine begins recording the PCM data. The routine
;---|*| returns immediately. Subsequent call must be made to
;---|*| "ContinueBlockInput" to receive data from the DMA buffer.
;---|*|
;---|*| int StartFileOutput ( FILE *f, long )
;---|*|
;---|*| This routine begins playing the PCM data from the disk file.
;---|*| The routine returns immediately. The routine,
;---|*| "ContinueFileOutput" must be called to continue moving data
;---|*| from the disk to the DMA buffer. The long variable tells how
;---|*| many bytes to play.
;---|*|
;---|*| int StartBlockOutput ( char far * )
;---|*|
;---|*| This routine begins playing the caller's PCM data. The
;---|*| routine returns immediately. The routine, "ContinueBlockOutput"
;---|*| must subsequently be called to continue data output.
;---|*|
;---|*| int ContinueFileInput ( )
;---|*|
;---|*| This routine checks to see if new data has been loaded into
;---|*| the linked list of buffers. If so, the data is written to
;---|*| disk, and the buffer is freed up.
;---|*|
;---|*| int ContinueBlockInput ( char far * )
;---|*|
;---|*| This routine checks to see if new data has been loaded into
;---|*| the linked list of buffers. If so, the data is written to
;---|*| the caller's buffer.
;---|*|
;---|*| int ContinueFileOutput ( )
;---|*|
;---|*| This routine checks to see if the PCM hardware is
;---|*| still playing. This routine MUST be called frequently to
;---|*| maintain continuous PCM output.
;---|*|
;---|*| int ContinueBlockOutput (char far *)
;---|*|
;---|*| This routine checks to see if the PCM hardware is
;---|*| still playing. The caller passes the next block to be
;---|*| played. A non-zero return value indicates the block has
;---|*| been queued up to be played. A zero value means the buffer
;---|*| is currently full, please try again...
;---|*|
;---|*| void StopDMAIO ( )
;---|*|
;---|*| This routine is used to prematurely terminate PCM I/O.
;---|*|
;---|*| void ClosePCMBuffering ( )
;---|*|
;---|*| This routine is used to close down the whole PCM I/O system.
;---|*| This call MUST be made before the caller's program terminates.
;---|*|
; \*/
; /*\
;---|*|----====< Code Generation >====----
; \*/
#define BLOCKOUT 0 /* builds block output code only */
#define BLOCKIN 0 /* builds block input code only */
#define FILEOUT 0 /* builds file output code only */
#define FILEIN 0 /* builds file input code only */
#define COMMDATA 0 /* builds both common code and data */
#ifdef BUILDBO
#undef BLOCKOUT
#define BLOCKOUT 1
#endif
#ifdef BUILDBI
#undef BLOCKIN
#define BLOCKIN 1
#endif
#ifdef BUILDFO
#undef FILEOUT
#define FILEOUT 1
#endif
#ifdef BUILDFI
#undef FILEIN
#define FILEIN 1
#endif
#ifdef BUILDCO
#undef COMMDATA
#define COMMDATA 1
#endif
; /*\
;---|*|----====< common data for CODE and DATA generation >====----
; \*/
/* buffer linked list header structures */
typedef struct _buffptr {
int status; /* 0=empty, 1=full */
int count; /* # of bytes in the buffer */
int size; /* total size of allocated buff */
char huge *buffer; /* pointer to buffer data */
struct _buffptr far *nextptr; /* pointer to next buffer hdr */
} BuffData, far *BuffPtr;
#define NODIRECTION 0 /* defines for DirectionFlag */
#define DMAINPUT 1
#define DMAOUTPUT 2
; /*\
;---|*|----====< Global Data >====----
; \*/
#define QUEUESIZE 32 /* 32 entries */
#define QUEUEMASK 0x1F /* mask to circulate the count */
#if COMMDATA
unsigned int MaxBuffCount = 0; /* # of DMA buffer divisions */
unsigned int BufferSize = 0; /* size of each buffer division */
/* shared global variables between the two tasks (in pcmioa.asm) */
BuffPtr HeadOfBuffers = 0; /* global variable head pointer */
int BufferDataCount = 0; /* # of full buffers (0=done) */
int DMARunning = 0; /* DMA status (0=off,1=running) */
char huge *DMABuffPtr = 0; /* 128k+1 DMA buffer pointer */
char far *StartOfDMABuffer = 0; /* start of DMA buffer pointer */
int ProcessedBlockCount = 0; /* # of I/O blocks processed */
unsigned long _file_data_length = 0;/* size of data output */
char __pcmdatasize = 8; /* default to 8 bit pcm */
FILE *__fptr = 0; /* file pointer for disk I/O */
long __fptrpos = 0; /* file pointer position */
BuffPtr __NextPtr = 0; /* next buffer pointer */
int __DirectionFlag = 0; /* current I/O direction */
char far * __singleblockpointer = 0;/* single shot users buffer */
/* local data for this body of code, but needs to be public */
int VoiceActivatedSavedCount = 0; /* # of I/O blocks saved */
int __queuein = 0;
int __queueincnt = 0;
int __queueout = 0;
long __queuedata = 0;
char far *__queuebuff[QUEUESIZE] = // number of queued blocks
{ 0 };
long __queuelen[QUEUESIZE] = // queued block lengths
{ 0 };
void (far * __queuecb[QUEUESIZE])()=// queue of callback routines
{ 0 };
void (far *__synccallback)() = 0; // callback to user code
#else
extern unsigned int MaxBuffCount; /* # of DMA buffer divisions */
extern unsigned int BufferSize; /* size of each buffer division */
/* shared global variables between the two tasks (in pcmioa.asm) */
extern BuffPtr HeadOfBuffers; /* global variable head pointer */
extern int BufferDataCount; /* # of full buffers (0=done) */
extern int DMARunning; /* DMA status (0=off,1=running) */
extern char huge *DMABuffPtr; /* 128k+1 DMA buffer pointer */
extern char far *StartOfDMABuffer; /* start of DMA buffer pointer */
extern int ProcessedBlockCount; /* # of I/O blocks processed */
extern unsigned long _file_data_length; /* size of data output */
extern char __pcmdatasize; /* default to 8 bit pcm */
extern FILE *__fptr; /* file pointer for disk I/O */
extern long __fptrpos; /* file pointer position */
extern BuffPtr __NextPtr; /* next buffer pointer */
extern int __DirectionFlag; /* current I/O direction */
extern char far* __singleblockpointer;/* single shot users buffer */
extern int VoiceActivatedSavedCount;/* # of I/O blocks saved */
extern int __queuein;
extern int __queueincnt;
extern int __queueout;
extern long __queuedata;
extern char far *__queuebuff[]; // number of queued blocks
extern long __queuelen[]; // queued block lengths
extern void (far * __queuecb[])(); // queue of callback routines
extern void (far *__synccallback)();// callback to user code
extern int __debugdw ();
#endif
/* additional prototypes */
void far * _rfmemcpy ( void far *, void far *, unsigned int );
void huge * _rfhmemcpy ( void huge *,void huge *,unsigned int );
void far * _memmalloc ( long );
void _memmfree ( void far * );
int _dofread ( char far *, int, int );
int _dofwrite ( char far *, int, int );
#if BLOCKOUT
static int _loadtheblock ( char far * );
#endif
#if FILEOUT
static int _loadthebuffer ();
#endif
; /*\
;---|*|-----------------====================================-----------------
;---|*|-----------------====< Start of Executable Code >====-----------------
;---|*|-----------------====================================-----------------
; \*/
#if FILEIN
; /*\
;---|*|----====< ASpecialContinueFileInput >====----
;---|*|
;---|*| This is a special adaptation of the standard, "ContinueDMAInput"
;---|*| routine. It will check the noise level in each block before writting
;---|*| it out to disk. This way, no data is written until a noise level
;---|*| is reached.
;---|*|
; \*/
int ASpecialContinueFileInput(noise,goflag)
int noise; /* offset from silence */
int goflag; /* record all after first block */
{
int temp;
/* if BufferDataCount is non-zero, we must process the DMA data */
while (BufferDataCount) {
/* validate the level of noise before writing it to disk */
if (MakeHalfHistoGram(__NextPtr->buffer,BufferSize,noise) ||
(VoiceActivatedSavedCount && goflag) ) {
/* if not all data is written, return in error */
if (_dofwrite (__NextPtr->buffer,BufferSize,fileno(__fptr)) != BufferSize) {
StopDMAIO();
return (0);
}
VoiceActivatedSavedCount++;
}
else
ProcessedBlockCount--;
/* move to the next buffer */
__NextPtr->count = __NextPtr->status = 0;
__NextPtr = __NextPtr->nextptr;
BufferDataCount--;
}
/* if at the end of this block, reposition the stream pointer */
if (!DMARunning)
fseek ( __fptr, __fptrpos, SEEK_SET );
return (DMARunning);
}
#endif
#if COMMDATA
; /*\
;---|*|----====< ClosePCMBuffering >====----
;---|*|
;---|*| Removes the PCM system & deallocates the buffer memory. There is
;---|*| no return value.
;---|*|
; \*/
void ClosePCMBuffering()
{
BuffPtr p,op;
//__debugdw (0x01);
/* we will kill the DMA low level processing */
StopDMAIO();
_unloadirqvector();
/* Free up the linked list of buffers */
if ((p = HeadOfBuffers) != 0) {
do {
op = p; /* save the old ptr */
p = p->nextptr; /* point to the next buffer */
_memfree (op); /* free up the old header */
} while ( (p != HeadOfBuffers) && p );
}
/* free up the DMA buffer */
if (DMABuffPtr)
_memfree (DMABuffPtr);
/* null it all out... */
DMABuffPtr = 0;
HeadOfBuffers = 0;
StartOfDMABuffer = 0;
BufferDataCount = BufferSize = DMARunning = 0;
}
#endif
#if BLOCKIN
; /*\
;---|*|----====< ContinueBlockInput >====----
;---|*|
;---|*| This routine checks to see if another buffer can be stored in memory.
;---|*| if so, it will load copy the DMA buffer to the caller's local buffer,
;---|*| A return value of 0 indicates the caller's buffer is empty.
;---|*|
; \*/
int ContinueBlockInput(buff)
char far *buff;
{
/* if BufferDataCount is non-zero, we must move the data to memory */
if (BufferDataCount) {
/* data is available, just move it out */
_rfmemcpy (buff,__NextPtr->buffer,BufferSize);
/* move to the next buffer */
__NextPtr->count = __NextPtr->status = 0;
__NextPtr = __NextPtr->nextptr;
BufferDataCount--;
/* returns the fact that the data has been loaded */
return(1);
}
return (0);
}
; /*\
;---|*|----====< ContinueThisBlockInput >====----
;---|*|
;---|*| This routine extracts a DMA buffer into one or
;---|*| more target user buffers.
;---|*|
;---|*| Returns:
;---|*| Nonzero for running & processing, else 0 for dead.
;---|*|
; \*/
int ContinueThisBlockInput()
{
unsigned int n, // working integer
loop, // loop flag to keep loading blocks
bcount; // increments the BufferDataCount
unsigned int result = 0; // holds the final count
static unsigned int TargetSize; // remaining size of the target dma buffer
static char far *dmaptr; // pointer to this DMA block
// if the DMA is dead, give it a jump start. Bad thing, it flushes all...
if (DMARunning == 0) {
// blow off anything that is saved locally
dmaptr = 0;
TargetSize = 0;
// reset and restart the low level stuff...
_resetbuffers();
StartTheDMAInput(ContinueThisBlockInput);
// we have no more data, just return now
return(DMARunning);
}
// if the current remaining length is null, prime for the next block
if (_file_data_length == 0) {
// bomb out if no data buffers queued up
if (__queueincnt == 0)
return(1);
// get the next block from the queue
_file_data_length = __queuelen [__queueout];
__singleblockpointer = __queuebuff[__queueout];
}
// loop here to stuff as many blocks as possible into the DMA buffer
nextblock:
// move up to one buffer division worth of data
if (!TargetSize) {
dmaptr = __NextPtr->buffer;
TargetSize = BufferSize;
}
loop = TRUE;
bcount = 1;
// move as many blocks as possible into the DMA buffer
while (loop) {
// Get the block length, up to the division size
if (_file_data_length <= TargetSize) {
n = _file_data_length; // full target size
_file_data_length = 0;
}
else // partial target size
_file_data_length -= (n = TargetSize);
// copy the data to the buffer, and advance the buffer that far
if (n) {
// move the recorded data into the buffer
__singleblockpointer
= _rfmemcpy(__singleblockpointer,dmaptr,n);
dmaptr += n; // move the dma pointer
result += n; // more for the return value
__queuedata -= (n &0xffff); // less queued up
BufferDataCount -= bcount; // increment buffer count once
bcount = 0;
}
// if the length is zero, this buffer is done, let the caller know
if (!_file_data_length) {
// let the app. know we are done with this buffer
if (__queuecb[__queueout])
(*__queuecb[__queueout])(__queuebuff[__queueout],__queuelen[__queueout]);
__queueincnt--;
__queueout = ++__queueout & QUEUEMASK;
// Now, try to get the next available block out of the list
if (__queuein != __queueout) {
_file_data_length = __queuelen[__queueout];
__singleblockpointer = __queuebuff[__queueout];
}
else
loop = FALSE;
}
// we are now done with this much of the buffer, stop if zero
if (!(TargetSize -= n))
loop = FALSE;
}
// advance the list to the next DMA buffer and count one more...
__NextPtr = __NextPtr->nextptr;
// if we can do more, then DO IT!!!
if (BufferDataCount > 0) { // if there is data in the DMA...
if (__queueincnt) // if we have buffers...
goto nextblock; // then go load it...
}
// return the number of bytes loaded
return(result);
}
#endif
#if FILEIN
; /*\
;---|*|----====< ContinueFileInput >====----
;---|*|
;---|*| This routine checks to see if another buffer can be written to disk.
;---|*| if so, it will load copy the buffer to a local buffer, then write it
;---|*| out to disk. A return value of 0 indicates recording has stopped,
;---|*| which could mean that the disk file is full, so the DMA had to be
;---|*| stopped prematurely.
;---|*|
; \*/
int ContinueFileInput()
{
/* if BufferDataCount is non-zero, we must write out the data */
while (BufferDataCount) {
/* data is available, move it out to disk */
/* if not all data is written, return in error */
if (_dofwrite (__NextPtr->buffer,BufferSize,fileno(__fptr)) != BufferSize) {
StopDMAIO();
return (0);
}
/* move to the next buffer */
__NextPtr->status = 0;
__NextPtr = __NextPtr->nextptr;
BufferDataCount--;
}
/* if at the end of this block, reposition the stream pointer */
if (!DMARunning)
fseek ( __fptr, __fptrpos, SEEK_SET );
return (DMARunning);
}
#endif
#if BLOCKOUT
; /*\
;---|*|----====< ContinueBlockOutput >====----
;---|*|
;---|*| This routine checks to see if another DMA buffer can be loaded.
;---|*| If so, it will load the user's block data into an empty buffer.
;---|*| A return value of 1 indicates the buffer has been loaded, else
;---|*| it must be sent in again until it is loaded.
;---|*|
; \*/
int ContinueBlockOutput(buff)
char far *buff;
{
/* if the internal count is not max-ed out, try to load the next buffer */
if (BufferDataCount < MaxBuffCount ) {
_loadtheblock (buff);
if (DMARunning == 0) { /* yuck! a DMA break! */
_resetbuffers();
StartTheDMAOutput(0);
}
return (1); /* return running */
}
else
return(0);
}
; /*\
;---|*|----====< ContinueThisBlockOutput >====----
;---|*|
;---|*| This routine checks to see if another DMA buffer can be loaded.
;---|*| If so, it will load the user's block data into an empty buffer.
;---|*| A return value of ~0 indicates the buffer has been loaded.
;---|*|
;---|*| The foreground routine will call this when DMARunning == 0. The
;---|*| background routine will call this at every interrupt to keep the
;---|*| buffers loaded
;---|*|
; \*/
int ContinueThisBlockOutput()
{
unsigned int n, // working integer
TargetSize, // size of the target dma buffer
loop, // loop flag to keep loading blocks
bcount; // increments the BufferDataCount
unsigned int result = 0; // holds the final count
char far *s;
// If no more data to load in the buffer, flush the next DMA & return
if (__queueincnt == 0) {
//__debugdw (0x20);
FlushBuffer (__NextPtr->buffer,BufferSize);
__NextPtr = __NextPtr->nextptr;
return(0);
}
// if there is little data, but a lot in the DMA, just return
if ((__queuedata < BufferSize) && (BufferDataCount > 2)) {
//__debugdw (0x21);
return(0);
}
// if the DMA has been turned off, re-sync the buffers
if (DMARunning == 0) {
//__debugdw (0x29);
_resetbuffers();
}
// if the current remaining length is null, prime for the next block
if (_file_data_length == 0) {
//__debugdw (0x2A,__queueout);
_file_data_length = __queuelen [__queueout];
__singleblockpointer = __queuebuff[__queueout];
}
// loop here to stuff as many blocks as possible into the DMA buffer
nextblock:
// move up to one buffer division worth of data
TargetSize = BufferSize;
s = __NextPtr->buffer;
loop = TRUE;
bcount = 1;
// move as many blocks as possible into the DMA buffer
while (loop) {
//__debugdw (0x22);
// Get the block length, up to the division size
if (_file_data_length <= TargetSize) {
n = _file_data_length; // full target size
_file_data_length = 0;
}
else // partial target size
_file_data_length -= (n = TargetSize);
//__debugdw (0x23,n);
// copy the data to the buffer, and advance the buffer that far
if (n) {
s = _rfmemcpy(s, __singleblockpointer, n );
result += n; // more for the return value
__singleblockpointer += n; // advance the pointer
__queuedata -= (n &0xffff); // less queued up
BufferDataCount += bcount; // increment buffer count once
bcount = 0;
}
else
s = __NextPtr->buffer; // no data, but do point here
// if the length is zero, this buffer is done, let the caller know
if (!_file_data_length) {
//__debugdw (0x24);
// if this old block was valid, send a DONE msg.
if(__queueincnt) {
// let the app. know we are done with this buffer
if (__queuecb[__queueout])
(*__queuecb[__queueout])(__queuebuff[__queueout],FALSE);
__queueincnt--;
__queueout = ++__queueout & QUEUEMASK;
}
// Now, try to get the next available block out of the list
if (__queuein == __queueout) {
//__debugdw (0x25);
FlushBuffer (s,TargetSize-n);
loop = FALSE;
}
else {
//__debugdw (0x26);
_file_data_length = __queuelen[__queueout];
__singleblockpointer = __queuebuff[__queueout];
}
}
// we are now done with this much of the buffer, stop if zero
if (!(TargetSize -= n))
loop = FALSE;
}
// advance the list to the next DMA buffer and count one more...
__NextPtr = __NextPtr->nextptr;
// if we can do more, then DO IT!!!
if (BufferDataCount < MaxBuffCount) { // if there is room in the DMA
if (__queueincnt) { // if we have pcm data
if (__queuedata >= BufferSize){ // and its GE a buffer division,
//__debugdw (0x27);
goto nextblock; // then go load it...
}
}
}
if (DMARunning == 0) {
//__debugdw (0x28);
StartTheDMAOutput(ContinueThisBlockOutput);
}
// return the number of bytes loaded
return(result);
}
#endif
#if FILEOUT
; /*\
;---|*|----====< ContinueFileOutput >====----
;---|*|
;---|*| This routine checks to see if another buffer can be loaded. If so, it
;---|*| will load the data into an empty buffer. All empty buffers will be
;---|*| loaded. A return value of 0 indicates playing has finished.
;---|*|
; \*/
int ContinueFileOutput()
{
/* if BufferDataCount is not max-ed out, try to load the next buffer*/
if (BufferDataCount < MaxBuffCount ) {
if (_loadthebuffer()) {
if (DMARunning == 0) { /* yuck! a DMA break! */
_resetbuffers();
if (StartTheDMAOutput(0))
return(0);
}
}
}
/* if at the end of this block, reposition the stream pointer */
if (!DMARunning)
fseek ( __fptr, __fptrpos, SEEK_SET );
return (DMARunning); /* return the DMA state */
}
#endif
#if COMMDATA
; /*\
;---|*|----====< OpenPCMBuffering >====----
;---|*|
;---|*| This routine is the first-call routine. It initializes the buffers
;---|*| needed for the PCM play/record system. A return value of non-zero
;---|*| indicates a failure to initialize the system.
;---|*|
;---|*| Entry Conditions:
;---|*|
;---|*| dma -- New DMA #. (1-3, or -1 for no changes)
;---|*| irq -- New IRQ #. (3,5,6,7, or -1 for no changes)
;---|*|
;---|*| Exit Conditions:
;---|*|
;---|*| non-zero return indicates an error
;---|*|
; \*/
int OpenPCMBuffering(dma,irq,dmasize,divisions)
int dma; /* DMA channel # (-1 for no changes) */
int irq; /* IRQ channel # (-1 for no changes) */
int dmasize; /* requested DMA size (4/8/16/32/64) */
int divisions; /* # of divisions in the DMA buffer */
{
BuffPtr op,p;
long l;
int n;
char far *db;
//__debugdw (0x00);
/* setup the globa variables & a local buffer */
MaxBuffCount = divisions;
BufferSize = LONG(dmasize/divisions) * 1024L;
/* Setup the lowlevel routines */
InitMVSound();
/* flush any background task setup */
BackgroundInit( BufferSize, MaxBuffCount );
/* Allocate twice the size for background DMA buffer */
l = LONG(dmasize) * 1024 * 2;
if ((DMABuffPtr = (char huge *)_memmalloc(l)) == 0)
return(PCMIOERR_NOMEM);
if ((db=StartOfDMABuffer=FindDMABuffer(DMABuffPtr,dmasize)) == 0)
return (PCMIOERR_OPENPCM);
/* if the low level code doesn't like it, bomb out */
if (!DMABuffer ( StartOfDMABuffer, dmasize, MaxBuffCount ))
return(PCMIOERR_OPENPCM);
/* Attempt to allocate each foreground buffer */
op = 0;
for (n=0;n<divisions;n++) {
/* allocate the linked list header for each buffer */
if ((p = (BuffPtr)_memmalloc (sizeof(BuffData))) == 0)
return(PCMIOERR_NOMEM);
/* reset the pointer in case of other failures during init */
p->nextptr = 0;
/* if first block, save as the head of the list */
if (!HeadOfBuffers)
HeadOfBuffers = p;
/* if we have already allocated a block, setup the fwd ptr */
if (op)
op->nextptr = p;
p->buffer = db;
p->size = BufferSize;
db += BufferSize;
/* save as the old pointer for linking purposes */
op = p;
}
/* link the last buffer back to the first */
p->nextptr = HeadOfBuffers;
/* Possibly select new DMA & IRQ channels */
if (dma != -1)
if (SelectDMA(dma))
return(PCMIOERR_BADDMA);
if (irq != -1)
if (SelectIRQ(irq))
return(PCMIOERR_BADIRQ);
/* well, it looks good so far, flush any variables */
BufferDataCount = ProcessedBlockCount =
_file_data_length = __queuedata =
VoiceActivatedSavedCount = __queueincnt =
__queuein = __queueout = 0;
/* and return good! */
return (0);
}
#endif
#if COMMDATA
; /*\
;---|*|----====< PCMState >====----
;---|*|
;---|*| This routine passes in the sample rate, stereo/mono flag, and any
;---|*| other miscellaneous data (to be determined later...)
;---|*|
;---|*| Exit Conditions:
;---|*| Non-zero means the sample rate was in error.
;---|*| Zero means the sample rate was okay error.
;---|*|
; \*/
int PCMState(sr,sm,cp,sz)
long sr; /* sample rate */
int sm; /* stereo/mono */
int cp; /* compression */
int sz; /* size(8/16) */
{
//__debugdw (0x02);
/* just pass them on... */
__pcmdatasize = sz; /* pcm data size */
return (PCMInfo(sr,sm,cp,sz) ? PCMIOERR_SAMPLERATE : 0 );
}
#endif
#if COMMDATA
; /*\
;---|*|----====< QueryPCMStream >====----
;---|*|
;---|*| Returns the number of blocks playing in the DMA buffer and
;---|*| the number of blocks queued up.
;---|*|
;---|*| Exit Conditions:
;---|*| 0 - x is the number of blocks waiting to be played
;---|*| -1 DMA is stopped, no more data
;---|*|
; \*/
int QueryPCMStream()
{
//__debugdw (0x15,__queueincnt+BufferDataCount);
// done if no DMA activity
if (!DMARunning)
return(-1);
// return the count of active blocks
return (__queueincnt+BufferDataCount);
}
#endif
#if COMMDATA
; /*\
;---|*|----====< StopDMAIO >====----
;---|*|
;---|*| This routine forceably kills the PCM I/O. All buffers will be
;---|*| reset, the current position of the input file is not altered. There
;---|*| is no return value.
;---|*|
; \*/
void StopDMAIO()
{
//__debugdw (0x03);
/* if this code has not already been setup, exit now */
if (!HeadOfBuffers)
return;
/* stop the hardware... */
StopPCM( );
__queuein = __queueincnt = __queueout = DMARunning = 0;
__queuedata = _file_data_length = 0;
/* flush any prior background task setup */
////if (__DirectionFlag == DMAOUTPUT) {
//// if (__fptr) {
//// rewind (__fptr);
//// __fptrpos = 0;
//// }
////}
/* reset the linked list of buffers */
_resetbuffers();
/* setup our internal direction flag */
__DirectionFlag = NODIRECTION;
}
#endif
#if BLOCKIN
; /*\
;---|*|----====< StartBlockInput >====----
;---|*|
;---|*| This routine resets the buffer pointers, then starts up
;---|*| the DMA PCM input. Nothing else needs to be done. A return
;---|*| value of 0 indicates the DMA failed to startup; No input
;---|*| is occuring.
;---|*|
; \*/
int StartBlockInput()
{
/* setup our internal direction flag */
__DirectionFlag = DMAINPUT;
/* Reset the # of blocks seen during I/O processing */
ProcessedBlockCount = 0;
/* Flush all buffers */
_resetbuffers();
/* if the hardware level code isn't gonna work, then return a failure. */
return (!StartTheDMAInput(0));
}
; /*\
;---|*|----====< RecordThisBlock >====----
;---|*|
;---|*| This routine offers the caller a simplified recording of one
;---|*| variable length block of data. The call just needs to call
;---|*| OpenPCMBuffering, then RecordThisBlock. The caller just has
;---|*| to poll DMARunning to see if the block has completed. Calling
;---|*| StopDMAIO will stop the process, if need be.
;---|*|
; \*/
int RecordThisBlock(p,len,cb)
char far *p;
unsigned long len;
void (far *cb)();
{
int n;
// if the pointer is valid, then queue it up
if (p) {
// return if already full
if (__queuein == QUEUESIZE)
return(2);
// extract the 1st entry from the queue
__queuebuff[__queuein] = p;
__queuedata += (__queuelen [__queuein] = len);
__queuecb [__queuein] = cb;
__queuein = ++__queuein & QUEUEMASK;
__queueincnt++;
}
// if the blocks are not recording , the let'er rip...
if ((DMARunning == 0) && __queueincnt ) {
// setup the direction flag
__DirectionFlag = DMAINPUT;
/* reset the DMA block pointers */
_resetbuffers();
/* return good or bad if the PCM engine is running */
return (ContinueThisBlockInput() ? 1 : 0 );
}
// assume the block is now recording
return(0);
}
#endif
#if FILEIN
; /*\
;---|*|----====< StartFileInput >====----
;---|*|
;---|*| This routine resets the buffer pointers, then starts up the DMA PCM
;---|*| input. Nothing else needs to be done.
;---|*|
; \*/
int StartFileInput(f)
FILE *f;
{
/* save our local file handle */
__fptr = f;
__fptrpos = ftell(f);
/* setup our internal direction flag */
__DirectionFlag = DMAINPUT;
/* Reset the # of blocks seen during I/O processing */
ProcessedBlockCount = 0;
/* Flush all buffers and other stuff.. */
_resetbuffers();
VoiceActivatedSavedCount = 0;
/* start the DMA engine */
return (!StartTheDMAInput(0));
}
#endif
#if BLOCKOUT
; /*\
;---|*|----====< StartBlockOutput >====----
;---|*|
;---|*| This routine allocates and loads the necessary buffers with data from
;---|*| the PCM disk file. Upon return, if there is data available, the
;---|*| background task will be called to start the DMA. The file handle will
;---|*| be saved in a global variable to be access from other foreground
;---|*| routines. a non-zero return value indicates PCM output is playing.
;---|*|
; \*/
int StartBlockOutput(buff)
char far *buff;
{
/* setup our internal direction flag */
__DirectionFlag = DMAOUTPUT;
/* Reset the # of blocks seen during I/O processing */
ProcessedBlockCount = 0;
/* load the DMA buffers */
_resetbuffers();
_loadtheblock (buff);
/* return good or bad if the engine is started */
return (!StartTheDMAOutput(0));
}
; /*\
;---|*|----====< PlayThisBlock >====----
;---|*|
;---|*| This routine offers the caller a simplified playback of one
;---|*| variable length block of data. The call just needs to call
;---|*| OpenPCMBuffering, then PlayThisBlock. The caller just has
;---|*| to poll DMARunning to see if the block has completed. Calling
;---|*| StopDMAIO will stop the process, if need be.
;---|*|
;---|*| Also see QueueThisBlock.
;---|*|
;---|*| Entry:
;---|*| p is the far pointer to a block of data (64k max size). If
;---|*| the pointer is null, the block will not be queued.
;---|*| len is the length of the block in bytes (one based count).
;---|*| cb is the callback routine to call when the block is empty.
;---|*|
;---|*| Returns:
;---|*| 0 - block is queued and playing
;---|*| 1 - Block failed to start
;---|*| 2 - queue is full, try later
;---|*|
; \*/
int PlayThisBlock(p,len,cb)
char far *p;
unsigned long len;
void (far *cb)();
{
int n;
// if the pointer is valid, then queue it up
if (p) {
// return if already full
if (__queuein == QUEUESIZE)
return(2);
// extract the 1st entry from the queue
__queuebuff[__queuein] = p;
__queuedata += (__queuelen [__queuein] = len);
__queuecb [__queuein] = cb;
__queuein = ++__queuein & QUEUEMASK;
__queueincnt++;
//__debugdw (0x10,__queueincnt);
//__debugdw (0x14,BufferDataCount);
}
// if the blocks are not playing, the let'er rip...
if ((DMARunning == 0) && __queueincnt ) {
//__debugdw (0x11);
// setup to transfer this block
__DirectionFlag = DMAOUTPUT;
// start the process by loading the DMA buffers
return (ContinueThisBlockOutput() ? 0 : 1 );
}
// Indicate this has been queued up
return(0);
}
; /*\
;---|*|----====< QueueThisBlock >====----
;---|*|
;---|*| This routine will queue up one block, but not start the PCM
;---|*| transfers. Once X number of blocks are queued up, then the caller
;---|*| can call PlayThisBlock/RecordThisBlock to start the process.
;---|*|
;---|*| Also see PlayThisBlock/RecordThisBlock.
;---|*|
;---|*| Entry:
;---|*| p is the far pointer to a block of data (64k max size).
;---|*| All pointers are assumed to be valid.
;---|*| len is the length of the block in bytes (one based count).
;---|*| cb is the callback routine to call when the block is empty.
;---|*|
;---|*| Returns:
;---|*| 0 - block is queued and playing
;---|*| 2 - queue is full, try later
;---|*|
; \*/
int QueueThisBlock(p,len,cb)
char far *p;
unsigned long len;
void (far *cb)();
{
int n;
// return if already full
if (__queuein == QUEUESIZE) {
//__debugdw (0x12,__queueincnt);
return(2);
}
// if idle, setup our internal direction flag, and the desired length
__queuebuff[__queuein] = p;
__queuedata += (__queuelen [__queuein] = len);
__queuecb [__queuein] = cb;
__queuein = ++__queuein & QUEUEMASK;
__queueincnt++;
//__debugdw (0x12,__queueincnt);
// return all queued up
return(0);
}
; /*\
;---|*|----====< SyncCallBack >====----
;---|*|
;---|*| This routine will setup a callback to the caller's routine
;---|*| at the end of every DMA block interrupt
;---|*|
;---|*| Returns:
;---|*|
; \*/
int SyncCallBack(cb)
void (far *cb)();
{
int n;
// just save for a later call...
__synccallback = cb;
}
#endif
#if FILEOUT
; /*\
;---|*|----====< StartFileOutput >====----
;---|*|
;---|*| This routine allocates and loads the necessary buffers with data from
;---|*| the PCM disk file. Upon return, if there is data available, the
;---|*| background task will be called to start the DMA. The file handle will
;---|*| be saved in a global variable to be access from other foreground
;---|*| routines. a non-zero return value indicates PCM output is playing.
;---|*|
; \*/
int StartFileOutput(f,len)
FILE *f;
long len;
{
/* save our local file handle */
__fptr = f;
__fptrpos = ftell(f);
/* setup our internal direction flag, and the desired length */
__DirectionFlag = DMAOUTPUT;
_file_data_length = len;
/* Reset the # of blocks seen during I/O processing */
ProcessedBlockCount = 0;
/* if any data is loaded into the buffer, start the DMA & return */
_resetbuffers();
do {
/* get the next buffer full & exit if done */
if (!_loadthebuffer())
break;
} while (__NextPtr != HeadOfBuffers);
/* return good or bad if the engine is running */
return (!StartTheDMAOutput(0));
}
#endif
#if BLOCKOUT
; /*\
;---|*|----====< _loadtheblock >====----
;---|*|
;---|*| This routine loads the block into the DMA buffer.
;---|*|
; \*/
static int _loadtheblock(buff)
char far *buff;
{
/* load the block of data into the DMA buffer */
_rfmemcpy(__NextPtr->buffer, buff, BufferSize );
/* now that the data is secure, fill out the rest of the header to */
/* allow the background process to "see" this new data */
__NextPtr->status = 1;
__NextPtr->count = BufferSize;
__NextPtr = __NextPtr->nextptr; /* advance the list */
BufferDataCount++;
/* we have data, return the size */
return (BufferSize);
}
#endif
#if FILEOUT
; /*\
;---|*|----====< _loadthebuffer >====----
;---|*|
;---|*| This routine loads the disk contents into an available buffer.
;---|*| A return value of 0 indicates no more data has been loaded.
;---|*|
; \*/
static int _loadthebuffer()
{
char huge *s;
register int n;
long l;
/* reset the header data */
__NextPtr->count = __NextPtr->status = 0;
/* exit if there is no data to be read */
if (feof (__fptr) || (!_file_data_length))
return (0);
/* adjust the max count we want to process from the file */
if (_file_data_length <= BufferSize) {
l = _file_data_length;
_file_data_length = 0;
}
else {
_file_data_length -= (l = (BufferSize & 0xffff));
}
/* read the data from the file */
if((n = _dofread (__NextPtr->buffer, ((int)(l & 0xffff)),fileno(__fptr) )) == 0)
return(0);
s = __NextPtr->buffer+n; // point to the end of the block
// flush to the end if not a full buffer worth of data
if (n < BufferSize)
FlushBuffer (s,BufferSize-n);
/* now that the data is secure, fill out the rest of the header to */
/* allow the background process to "see" this new data */
__NextPtr->status = 1;
__NextPtr->count = BufferSize;
__NextPtr = __NextPtr->nextptr; /* advance the list */
BufferDataCount++;
/* we have data, return the size */
return (n);
}
#endif
#if COMMDATA
; /*\
;---|*|----====< _resetbuffers >====----
;---|*|
;---|*| This routine flushes the contents of the top level buffers
;---|*|
; \*/
_resetbuffers()
{
/* flush the count and status of every linked list block */
if ((__NextPtr = HeadOfBuffers) != 0) {
/* if there are buffers, reset them all */
do {
/* get the next buffer full & exit if done */
__NextPtr->count = __NextPtr->status = 0;
/* break when we reach the top */
if ((__NextPtr = __NextPtr->nextptr) == 0)
break;
} while (__NextPtr != HeadOfBuffers);
}
/* reset the global hand shake count. */
BufferDataCount = 0;
}
int __debugdw (int idx,int val)
{
#if 0
_asm {
push di
push es
sub di,di
mov es,di
les di,es:[0x194]
mov ax,es
or ax,ax
jz dedw05
cmp di,16768
jae dedw05
cld
mov ax,idx
stosb
mov ax,val
stosw
sub ax,ax
mov es,ax
mov es:[0x194],di
} dedw05: _asm {
pop es
pop di
}
#endif
}
#endif
; /*\
;---|*| end of PCMIOC.C
; \*/